package com.fsd.core.dao.impl;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.annotation.Resource;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.internal.CriteriaImpl;
import org.hibernate.internal.CriteriaImpl.OrderEntry;
import org.hibernate.metadata.ClassMetadata;
import org.hibernate.search.FullTextQuery;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.hibernate.search.SearchFactory;
import org.hibernate.search.query.dsl.MustJunction;
import org.hibernate.search.query.dsl.QueryBuilder;
import org.hibernate.transform.AliasToBeanResultTransformer;
import org.hibernate.transform.ResultTransformer;
import org.hibernate.transform.Transformers;
import org.springframework.util.Assert;
import com.fsd.core.dao.BaseDao;
import com.fsd.core.util.ParametersUtil;
import com.fsd.core.util.ReflectionUtil;
import com.fsd.core.util.Util;

/**
 * Dao实现类 - 基类
 * @author lumingbao
 */
public class BaseDaoImpl<T, PK extends Serializable> implements BaseDao<T, PK> {
	
	private static final String ORDER_LIST_PROPERTY_NAME = "orderList";// "排序"属性名称
	private static final String CREATE_DATE_PROPERTY_NAME = "createDate";// "创建日期"属性名称
	
	private Class<T> entityClass;
	protected SessionFactory sessionFactory;

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public BaseDaoImpl() {
        Class c = getClass();
        Type type = c.getGenericSuperclass();
        if (type instanceof ParameterizedType) {
            Type[] parameterizedType = ((ParameterizedType) type).getActualTypeArguments();
            this.entityClass = (Class<T>) parameterizedType[0];
        }
	}

	@Resource(name = "sessionFactory")
	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	protected Session getSession() {
		return sessionFactory.getCurrentSession();
	}
	
	@SuppressWarnings("unchecked")
	public T get(PK id) {
		Assert.notNull(id, "id is required");
		return (T) getSession().get(entityClass, id);
	}
	
	@SuppressWarnings("unchecked")
	public T load(PK id) {
		Assert.notNull(id, "id is required");
		return (T) getSession().load(entityClass, id);
	}
	
	@SuppressWarnings("unchecked")
	public List<T> getList(Criteria criteria){
		return criteria.list();
	}
	
	public Long getCount(String... condition) {
		String hql = "select count(*) from " + entityClass.getName();
		if(condition != null && condition.length > 0){
			hql += " where ";
			boolean _bool = false;
			for (String sql : condition) {
				if(_bool)
					hql += " and ";
				hql += sql;
				_bool = true;
			}
		}
		return (Long) getSession().createQuery(hql).uniqueResult();
	}
	
	public Long getCountEntity(String sql, Object... values){
		String hql = "select count(*) from " + entityClass.getName() + " where ";
		if(sql != null){
			hql += sql;
		}
		Query query = getSession().createQuery(hql);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {  
                query.setParameter(i, values[i]);  
            }
		}
		return (Long) query.uniqueResult();
	}
	
	@SuppressWarnings("unchecked")
	public PK save(T entity) {
		Assert.notNull(entity, "entity is required");
		return (PK) getSession().save(entity);
	}
	
	public void update(T entity) {
		Assert.notNull(entity, "entity is required");
		getSession().update(entity);
	}

	public void saveOrUpdate(T entity){
		Assert.notNull(entity, "entity is required");
		getSession().saveOrUpdate(entity);
	}
	
	public void delete(T entity) {
		Assert.notNull(entity, "entity is required");
		getSession().delete(entity);
	}
	
	@SuppressWarnings("unchecked")
	public void delete(PK id) {
		Assert.notNull(id, "id is required");
		T entity = (T) getSession().load(entityClass, id);
		getSession().delete(entity);
	}
	
	@SuppressWarnings("unchecked")
	public void delete(PK[] ids) {
		Assert.notEmpty(ids, "ids must not be empty");
		for (PK id : ids) {
			T entity = (T) getSession().load(entityClass, id);
			getSession().delete(entity);
		}
	}
	
	@SuppressWarnings("unchecked")
	public List<T> queryByHql(String hql, Object... values){
		Assert.notNull(hql, "hql must not be empty");
		Query query = getSession().createQuery(hql);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {  
                query.setParameter(i, values[i]);  
            }
		}
		return query.list();
	}
	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public List queryBySql(String sql, Object... values){
		Assert.notNull(sql, "sql must not be empty");
		Query query = getSession().createSQLQuery(sql);
		query.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {  
                query.setParameter(i, values[i]);  
            }
		}
		List newlist = new ArrayList();
		List list = query.list();
		for (int i = 0; i < list.size(); i++) {
			Map map = Util.keyToUpperCase((Map) list.get(i));
			newlist.add(i, map);
		}
		return newlist;
	}
	
	@SuppressWarnings("unchecked")
	public List<T> queryBySql1(String sql, Object... values){
		Assert.notNull(sql, "sql must not be empty");
		Query query = getSession().createSQLQuery(sql).addEntity(entityClass);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {  
                query.setParameter(i, values[i]);  
            }
		}
		return query.list();
	}
	
	public int executeHql(String hql, Object... values){
		Assert.notNull(hql, "hql must not be empty");
		Query query = getSession().createQuery(hql);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {  
                query.setParameter(i, values[i]);  
            }
		}
		return query.executeUpdate();
	}
	
	public int executeSql(String sql, Object... values){
		Assert.notNull(sql, "sql must not be empty");
		Query query = getSession().createSQLQuery(sql);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {  
                query.setParameter(i, values[i]);  
            }
		}
		return query.executeUpdate();
	}
	
	public void flush() {
		getSession().flush();
	}
	
	public void evict(Object object) {
		Assert.notNull(object, "object is required");
		getSession().evict(object);
	}

	public void clear() {
		getSession().clear();
	}
	
	public ParametersUtil findPager(ParametersUtil pager) {
		Criteria criteria = getSession().createCriteria(entityClass);
		return findPager(pager, criteria);
	}
	
	public ParametersUtil findPager(ParametersUtil pager, Criterion... criterions) {
		Criteria criteria = getSession().createCriteria(entityClass);
		for (Criterion criterion : criterions) {
			criteria.add(criterion);
		}
		return findPager(pager, criteria);
	}
	
	public ParametersUtil findPager(ParametersUtil pager, Order... orders) {
		Criteria criteria = getSession().createCriteria(entityClass);
		for (Order order : orders) {
			criteria.addOrder(order);
		}
		return findPager(pager, criteria);
	}
	
	public ParametersUtil findPager(ParametersUtil pager, Criteria criteria) {
		Assert.notNull(pager, "pager is required");
		Assert.notNull(criteria, "criteria is required");
		
		Integer pageNumber = pager.getPage();
		Integer pageSize = pager.getLimit();
		String searchBy = pager.getSearchBy();
		String keyword = pager.getKeyword();
		String orderBy = pager.getOrderBy();
		ParametersUtil.Order order = pager.getOrder();
		
		if (StringUtils.isNotEmpty(searchBy) && StringUtils.isNotEmpty(keyword)) {
			if (searchBy.contains(".")) {
				String alias = StringUtils.substringBefore(searchBy, ".");
				criteria.createAlias(alias, alias);
			}
			criteria.add(Restrictions.like(searchBy, "%" + keyword + "%"));
		}
		
		pager.setTotalCount(criteriaResultTotalCount(criteria));
		
		if (StringUtils.isNotEmpty(orderBy) && order != null) {
			if (order == ParametersUtil.Order.asc) {
				criteria.addOrder(Order.asc(orderBy));
			} else {
				criteria.addOrder(Order.desc(orderBy));
			}
		}
		
		ClassMetadata classMetadata = sessionFactory.getClassMetadata(entityClass);
		if (!StringUtils.equals(orderBy, ORDER_LIST_PROPERTY_NAME) && ArrayUtils.contains(classMetadata.getPropertyNames(), ORDER_LIST_PROPERTY_NAME)) {
			criteria.addOrder(Order.asc(ORDER_LIST_PROPERTY_NAME));
			criteria.addOrder(Order.desc(CREATE_DATE_PROPERTY_NAME));
			if (StringUtils.isEmpty(orderBy) || order == null) {
				pager.setOrderBy(ORDER_LIST_PROPERTY_NAME);
				pager.setOrder(ParametersUtil.Order.asc);
			}
		} else if (!StringUtils.equals(orderBy, CREATE_DATE_PROPERTY_NAME) && ArrayUtils.contains(classMetadata.getPropertyNames(), CREATE_DATE_PROPERTY_NAME)) {
			criteria.addOrder(Order.desc(CREATE_DATE_PROPERTY_NAME));
			if (StringUtils.isEmpty(orderBy) || order == null) {
				pager.setOrderBy(CREATE_DATE_PROPERTY_NAME);
				pager.setOrder(ParametersUtil.Order.desc);
			}
		}
		
		criteria.setFirstResult((pageNumber - 1) * pageSize);
		criteria.setMaxResults(pageSize);
		
		pager.setData(criteria.list());
		return pager;
	}
	
	// 获取Criteria查询数量
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	private int criteriaResultTotalCount(Criteria criteria) {
		Assert.notNull(criteria, "criteria is required");
		
		int criteriaResultTotalCount = 0;
		try {
			CriteriaImpl criteriaImpl = (CriteriaImpl) criteria;
			
			Projection projection = criteriaImpl.getProjection();
			ResultTransformer resultTransformer = criteriaImpl.getResultTransformer();
			List<OrderEntry> orderEntries = (List) ReflectionUtil.getFieldValue(criteriaImpl, "orderEntries");
			ReflectionUtil.setFieldValue(criteriaImpl, "orderEntries", new ArrayList());
			
			Integer totalCount = ((Long) criteriaImpl.setProjection(Projections.rowCount()).uniqueResult()).intValue();
			if (totalCount != null) {
				criteriaResultTotalCount = totalCount;
			}
			
			criteriaImpl.setProjection(projection);
			if (projection == null) {
				criteriaImpl.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
			}
			if (resultTransformer != null) {
				criteriaImpl.setResultTransformer(resultTransformer);
			}
			ReflectionUtil.setFieldValue(criteriaImpl, "orderEntries", orderEntries);
		} catch (Exception e) {
		
		}
		return criteriaResultTotalCount;
	}
	
	public String getUUID(){
		return UUID.randomUUID().toString().trim().replaceAll("-", "");
	}
	
	public Criteria createCriteria(){
		return getSession().createCriteria(entityClass);
	}
	
	public ParametersUtil search(ParametersUtil pager, Map<String, Object> param, String... filed) throws Exception{
		Integer pageNumber = pager.getPage();
		Integer pageSize = pager.getLimit();
		String keyWord = pager.getKeyword();
		FullTextSession fullTextSession = Search.getFullTextSession(sessionFactory.getCurrentSession());
		//fullTextSession.createIndexer().startAndWait();
		SearchFactory sf = fullTextSession.getSearchFactory();
		QueryBuilder qb = sf.buildQueryBuilder().forEntity(entityClass).get();
		
		MustJunction term = qb.bool().must(qb.keyword().onFields(filed).matching(keyWord).createQuery());
		
		for (String key : param.keySet()) {
			term.must(qb.keyword().onField(key).matching(param.get(key)).createQuery());
		}
		
		org.apache.lucene.search.Query luceneQuery  = term.createQuery();
		FullTextQuery hibQuery = fullTextSession.createFullTextQuery(luceneQuery);
		//hibQuery.setProjection(filed);//让查询建立在投影上  投影(Projection)
		hibQuery.setResultTransformer(new AliasToBeanResultTransformer(entityClass));
		hibQuery.setFirstResult((pageNumber - 1) * pageSize);
		hibQuery.setMaxResults(pageSize);
		
		pager.setTotalCount(hibQuery.getResultSize());  
		pager.setData(hibQuery.list());
		return pager;
	}
	
	/**
	 * 建立索引
	 */
	public void createIndex(){
		try {
			Search.getFullTextSession(sessionFactory.getCurrentSession()).createIndexer(entityClass).startAndWait();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 分页sql查询
	 * @param pager
	 * @param sqlTable 排序条件，例：tableName
	 * @param sqlOrder 排序条件，例：id asc, name desc
	 * @param sqlWhere 查询条件，例：id = ? and name = ?
	 * @param values 查询条件值
	 * @return
	 */
	public ParametersUtil queryBySqlPager(ParametersUtil pager, String sqlTable, String sqlOrder, String sqlWhere, Object... values){
		Assert.notNull(pager, "pager is required");
		Assert.notNull(sqlTable, "sql must not be empty");
		Assert.notNull(sqlWhere, "sql must not be empty");
		Assert.notNull(sqlOrder, "sql must not be empty");

		//https://www.cnblogs.com/FreeDong/archive/2011/09/27/2193240.html
		Integer pageNumber = pager.getPage();
		Integer pageSize = pager.getLimit();
//		String orderBy = pager.getOrderBy();
//		ParametersUtil.Order order = pager.getOrder();
		
		if (sqlWhere != null && !"".equals(sqlWhere)){
			sqlWhere = " where " + sqlWhere;
		}else{
			sqlWhere = "";
		}
		String hql = "select count(*) from " + sqlTable + sqlWhere;
		Query query = getSession().createSQLQuery(hql);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {  
                query.setParameter(i, values[i]);  
            }
		}
		Integer num = (Integer) query.uniqueResult();
		pager.setTotalCount(num);
		
//		SELECT TOP 15 * FROM(
//			    SELECT ROW_NUMBER() OVER(ORDER BY ID ASC) AS ROWID,* 
//			        FROM LeaveWordView) AS TEMP1 
//			    WHERE ROWID>10
		Integer maxID = (pageNumber - 1) * pageSize;
		if (sqlOrder != null && !"".equals(sqlOrder)){
			sqlOrder = " order by " + sqlOrder;
		}else{
			sqlOrder = "";
		}
		hql = "select top " + pageSize.toString() + 
				" * from (select row_number() over(" + sqlOrder + ") as rowid, " + 
				entityClass.getSimpleName() + ".* from " + sqlTable + sqlWhere + 
				" ) as " + entityClass.getSimpleName() + " where rowid > " + maxID.toString();
		query = getSession().createSQLQuery(hql).addEntity(entityClass);
		if (values != null) {
			for (int i = 0; i < values.length; i++) {  
                query.setParameter(i, values[i]);  
            }
		}
		pager.setData(query.list());
		return pager;
	}
}
